#include "abstract_camera.h"

#include "graphics/graphics.h"

#include "cam_controller_kb.h"

#include "math/plane.h"


#include "util/common_macros.h"		// macros
#include "util/log.h"		// macros
#include "util/mmgr.h"		// Needs to come in early

#include <cassert>

using namespace camera;

AbstractCamera* AbstractCamera::s_pCurrentCamera = NULL;


AbstractCamera::AbstractCamera( const math::Vec3& _From,const math::Vec3& _To,const math::Vec3& _Up,
                                float _FOV, float _Aspect, const math::Vec2& _ClipRange):
      m_Height(1),
      m_Width(1),    
      m_Stencil(false), 
      m_ZBuffer(false),
      m_pViewPort(NULL),
      m_pCamController(NULL)

{
    _LOG(MSG_INIT, "Camera created");
    SetLookAt(_From, _To, _Up);
    SetProjection(_FOV, _Aspect, _ClipRange);
    m_ClearColor = 0xFF000000;
}

AbstractCamera::~AbstractCamera()
{
    _LOG(MSG_COMMENT, "Camera Destroyed");
    UTIL_SAFE_DELETE(m_pCamController);
    DeleteViewport();
}

void AbstractCamera::CloneView(const AbstractCamera& _FromCamera)
{
    // Should we optimize this to do the matrix copies instead ?
    SetLookAt(_FromCamera.GetPos(), _FromCamera.GetPos() + _FromCamera.GetDir(), _FromCamera.GetUp());
    SetProjection(_FromCamera.GetFOV(), _FromCamera.GetAspect(), _FromCamera.GetClipRange());
}

void AbstractCamera::CloneReflection(const AbstractCamera& _FromCamera, const math::Plane& _Plane)
{
/*    D3DXMATRIXA16   matReflectedView;
    D3DXPLANE       plane(_Plane.m_Normal.x, _Plane.m_Normal.y, _Plane.m_Normal.z, _Plane.m_D);

    D3DXMatrixReflect( &matReflectedView, &plane );    
    math::Mat4x4    NewMat = matReflectedView * _FromCamera.GetWorldMat();

    SetLookAt(NewMat.GetPos(), NewMat.GetDir(), NewMat.GetUp());
    SetProjection(_FromCamera.GetFOV(), _FromCamera.GetAspect(), _FromCamera.GetClipRange());*/
//	FIXME: Reinstate
}



bool AbstractCamera::Clear(     bool _ClearRGBA, 
                                bool _ClearStencil,
                                bool _ClearDepth,
                                float _DepthClearVal,
                                unsigned long _StencilClearVal)
{
/*	LPDIRECT3DDEVICE9 _pDevice = GetGraphics()->GetDevice();
	DWORD Target = D3DCLEAR_TARGET & _ClearRGBA;
    Target      |= (_ClearStencil & HasStencil()) ? D3DCLEAR_STENCIL : 0;
    Target      |= (_ClearDepth & HasZBuffer()) ? D3DCLEAR_ZBUFFER : 0;
    if (Target) // did we actually find anything to clear ?
    {
        HRESULT hRes = _pDevice->Clear( 0, NULL, Target, m_ClearColor, _DepthClearVal, _StencilClearVal);
		assert(SUCCEEDED(hRes) && "Failed to clear Camera");
    }*/
	GetGraphics()->Clear(m_ClearColor, _DepthClearVal, _StencilClearVal, _ClearRGBA, _ClearDepth, _ClearStencil);
    return true;
}


void AbstractCamera::SetLookAt(const math::Vec3& _From, const math::Vec3& _To, const math::Vec3& _Up)
{
    m_Pos   = _From;
    m_Dir   = (_To - _From).Normalize();
    m_Up    = _Up;
    m_Up.Normalize();
    m_Right = m_Up * m_Dir;
	m_Right.Normalize();
	m_Up	= m_Dir * m_Right;
    m_Up.Normalize();

//	FIXME: Reinstate
//	D3DXMatrixLookAtLH( &m_World, &_From, &_To, &m_Up);

	// Resynch dependent matrices;
    ResynchMatrices();
}

void AbstractCamera::SetProjection(float _FOV, float _Aspect, const math::Vec2& _ClipRange)
{
    m_Projection.Identity();
	m_Aspect	= _Aspect;
	m_FOV		= _FOV;
	m_ClipRange = _ClipRange;

//	FIXME: Reinstate
/*
    D3DXMatrixPerspectiveFovLH(	&m_Projection, 
								(D3DX_PI/180.0f) * m_FOV,   // Field of view ?? degrees
								m_Aspect,                   // Aspect ratio
								m_ClipRange.x,              // Z Near
								m_ClipRange.y);				// Z Far

 */   // Resynch dependent matrices;
    ResynchMatrices();
}

void AbstractCamera::SetOrthographic(const math::Vec2& _Size, const math::Vec2& _ClipRange)
{
	m_Projection.Identity();
/*	D3DXMatrixOrthoLH(	&m_Projection, 
						_Size.x, _Size.y, 
						_ClipRange.x, _ClipRange.y);*/

//	FIXME: Reinstate
//	D3DXMatrixOrthoOffCenterLH(&m_Projection, 0, _Size.x, 0, _Size.y, _ClipRange.x, _ClipRange.y);
}


math::Vec3 AbstractCamera::ScaledScreenToVector( const math::Vec2& _Pos) const
{
    math::Vec3  v;
    v.x =  ( ( ( 2.0f * _Pos.x )  ) - 1 ) / m_Projection._11;
    v.y = -( ( ( 2.0f * _Pos.y) ) - 1 ) / m_Projection._22;
    v.z =  1.0f;

	math::Vec3  Dir;

    // Transform the screen space pick ray into 3D space
    Dir.x  = v.x*m_InvWorld._11 + v.y*m_InvWorld._21 + v.z*m_InvWorld._31;
    Dir.y  = v.x*m_InvWorld._12 + v.y*m_InvWorld._22 + v.z*m_InvWorld._32;
    Dir.z  = v.x*m_InvWorld._13 + v.y*m_InvWorld._23 + v.z*m_InvWorld._33;
	return Dir;
}

math::Vec3 AbstractCamera::ScreenToVector( const math::Vec2& _Pos) const
{
    math::Vec3  v;
    v.x =  ( ( ( 2.0f * _Pos.x ) / m_Width ) - 1 ) / m_Projection._11;
    v.y = -( ( ( 2.0f * _Pos.y) / m_Height ) - 1 ) / m_Projection._22;
    v.z =  1.0f;

	math::Vec3  Dir;

    // Transform the screen space pick ray into 3D space
    Dir.x  = v.x*m_InvWorld._11 + v.y*m_InvWorld._21 + v.z*m_InvWorld._31;
    Dir.y  = v.x*m_InvWorld._12 + v.y*m_InvWorld._22 + v.z*m_InvWorld._32;
    Dir.z  = v.x*m_InvWorld._13 + v.y*m_InvWorld._23 + v.z*m_InvWorld._33;
	return Dir;
}

math::Vec3 AbstractCamera::PositionToScreen(const math::Vec3&  _vPos) const
{
//	FIXME: Reinstate
/*
	D3DXVECTOR4		vScreen;
	D3DXVec3Transform(&vScreen, &_vPos, &m_WorldProj);

	vScreen.x = (vScreen.x / (vScreen.w*2.0f)) + 0.5f;
	vScreen.y = (-vScreen.y / (vScreen.w*2.0f)) + 0.5f;
	vScreen.z = (vScreen.z / (vScreen.w*2.0f));
    return math::Vec3(vScreen.x, vScreen.y, vScreen.z);*/
	return math::Vec3(0,0,0);
}

void        AbstractCamera::ResynchMatrices()
{
	// Recalculate Billboard Mat4x4 ?
	m_WorldProj	        = m_World * m_Projection;
	m_InvWorldProj      = !m_WorldProj;
	m_InvWorld			= !m_World;
    m_InvProj           = !m_Projection;

	// Recalc frustrum
	m_Frustum.CalculateFrustrum(m_World, m_Projection, false, true);
}


void          AbstractCamera::CreateViewport( int Width, int Height,
                                        int X, int Y,
                                        float MinZ, float MaxZ)
{
    
    if (!m_pViewPort)
        m_pViewPort = new math::Viewport;
    m_pViewPort->Height = Height;
    m_pViewPort->Width  = Width;
    m_pViewPort->X      = X;
    m_pViewPort->Y      = Y;
    m_pViewPort->MinZ   = MinZ;
    m_pViewPort->MaxZ   = MaxZ;
}

void           AbstractCamera::DeleteViewport()
{
    if (m_pViewPort)
    {
        UTIL_SAFE_DELETE(m_pViewPort);
    }
}

void            AbstractCamera::AttachController(CamController* pCont)
{
    UTIL_SAFE_DELETE(m_pCamController);
    m_pCamController = pCont;
}


void           AbstractCamera::AttachKBController()
{
    AttachController(new CamControllerKB());
}

void           AbstractCamera::AttachSpaceBallController()
{
	// add space ball controller ?
    AttachController(new CamControllerKB());
}

void        AbstractCamera::Tick(float dt)
{
    if (m_pCamController)
        m_pCamController->Tick(this, dt);
}
